# System modules
from queue import Queue
from threading import Thread
import time
import os
import re
import subprocess
import signal
import logging
import q2ex

class cmd(object):
    def __init__(self, tasks, cmdLine, threads, timeout, pluginOutputDir, outputExtension):
        self.tasks = tasks
        self.outputExtension = outputExtension
        self.cmdLine = cmdLine
        self.threads = int(threads)
        self.pluginOutputDirectory = pluginOutputDir
        self.to = int(timeout)
        self.task_queue = Queue()
        self.initWorkerThreads()
        self.populateQueue()
        self.task_queue.join()

    def initWorkerThreads(self):
         for i in range(self.threads):
             worker = Thread(target=self.performTask, args=(i, self.task_queue,))
             worker.daemon = True
             worker.start()

    def populateQueue(self):  
        for task_detail in self.tasks:
            self.task_queue.put(task_detail)
    
    def formatCommand(self, row):  
        finalcmd = ""
        tmpCmd = self.cmdLine
        regex = "\${[1-9]+}"  
        for i in re.findall(regex, tmpCmd):  
            index = "".join(re.findall("[0-9]+", i))
            tmpCmd = tmpCmd.replace(i,row[int(index)-1])
        finalcmd = tmpCmd
        logging.debug ('Executing: ' + finalcmd)
        return finalcmd

    def performTask(self, i, q):
       pid = ""
       while True:
           # Get the task from the queue
           task_details = q.get()
           # Format the command properly, replacing the placeholders with the values obtained from the database
           cmd = self.formatCommand(task_details)
           output = ""
           errors = ""
           absoluteOutputFileName = self.buildOutputFileName(task_details)
           try:
               # Run the command
               p = subprocess.Popen(cmd, shell=True)
               # find out the pid so that it can be used to kill the command e.g. if it reaches its timeout value
               pid = p.pid
               p.wait(timeout=self.to)
           except subprocess.TimeoutExpired:
               logging.warning ("Timeout on task has occurred -" + cmd)
               f = open(absoluteOutputFileName + ".timeout", 'w')
               f.write("")
               f.close()
               mh = os.environ['METIS_HOME']
               killcmd = mh + "utils/killtree.sh" 
               subprocess.Popen([killcmd, str(pid)]) 
           except OSError as oserr:
               logging.error ("Error executing command - " + str(oserr))
           except ValueError as verr:
               logging.error ("Error executing command - invalid arguments? - " + str(verr))
           except FileNotFoundError as ferr:
               logging.error ("Error executing command - file not found - " + str(ferr))
           q.task_done()

    def buildOutputFileName(self, task_details):
        outputFileName = '_'.join(task_details)
        outputFileName = self.pluginOutputDirectory + outputFileName + '.' + self.outputExtension
        return outputFileName

    def writeCommandOutput(self, absoluteOutputFileName, output, errors):
        logging.debug ("Output filename: " +  absoluteOutputFileName)
        f = open(absoluteOutputFileName, 'wb')
        f.write(output)
        f.close()
        # if there are errors to output, as there is length to the variable holding stderr then 
        if len(errors) != 0:
            logging.error (errors)
            f = open(absoluteOutputFileName + ".errors", 'wb')
            f.write(errors)
            f.close()

    # You need to use this, when handling a timeout exception, otherwise you get a  "TypeError: str does not support the buffer interface error"
    def writeCommandBufferOutput(self, absoluteOutputFileName, output, errors):
        logging.debug ("Output filename: " +  absoluteOutputFileName)
        f = open(absoluteOutputFileName, 'w', encoding='utf8')
        f.write(output)
        f.close()
        # if there are errors to output, as there is length to the variable holding stderr then 
        if len(errors) != 0:
            logging.error (errors)
            f = open(absoluteOutputFileName + ".errors", 'w', encoding='utf8')
            f.write(errors)
            f.close()
